#pragma once 

#include "Container.h"
#include "MessageMap.h"
#include "WndTipCtrl.h"

#include <assert.h>
#include <afxpriv.h>
#include <typeinfo>

// #define DEBUG_FOCUS

namespace My {

template <class T>
class Window : public T, public Container
{
public:
	Window();
	virtual ~Window();
public:
	UINT			m_Style;
	UINT			m_StyleEx;
	CString			m_Class;
protected:
	bool			m_ValidBounds;
	bool			m_Inside;
	Control *		m_pFocusControl; // what if the control is removed?
	Control *		m_pCaptureControl;
	WindowTipCtrl	m_HintWindow;
protected:
	virtual Control * GetCaptureControl() const
		{ return m_pCaptureControl; }
	virtual Control * GetFocusControl() const
		{ return m_pFocusControl; }
	virtual void SetCaptureControl(Control * pControl);
	virtual void SetFocusControl(Control * pControl);
public:
	using Control::ClientToScreen;
	using Control::ScreenToClient;
	using Control::Invalidate;
	CRect GetClientRect() const;
	int ClientWidth() const
		{ return GetClientRect().Width(); }
	int ClientHeight() const
		{ return GetClientRect().Height(); }
	virtual CSize ClientSize() const
		{ return GetClientRect().Size(); }
	virtual CRect Bounds() const;
	virtual bool Visible() const;
	virtual CPoint GetWindowOffset() const;
	virtual CRect GetWindowRect() const;
	virtual CWnd * GetWindow() const
		{ return m_hWnd != 0 ? const_cast<Window<T> *>(this) : 0; }
	virtual WindowTipCtrl * GetHintWindow() const
		{ return m_HintWindow.m_hWnd != 0 ? const_cast<WindowTipCtrl *>(&m_HintWindow) : 0; }
	virtual const CFont * Font() const {
		CFont * pFont = 0;
		if (m_hWnd != 0)
			pFont = T::GetFont();
		return pFont != 0 ? pFont : Control::Font(); 
	}
	virtual void CaptureMouse();
	virtual void ReleaseMouse();
	virtual bool HasCapture() const;
	virtual void SetFocus();
	virtual bool HasFocus() const;
	virtual void UpdateBounds();
	virtual void DoCreateWindow();
	void InvalidateWindowBounds();
	void UpdateWindowBounds();
protected:
	virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo);
	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); 
	afx_msg void OnDestroy(); 
	afx_msg void OnSize(UINT nType, int cx, int cy); 
	afx_msg void OnSetFocus(CWnd * pOldWnd);
	afx_msg void OnKillFocus(CWnd * pNewWnd);
	afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
	afx_msg void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags);
	afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnRButtonDblClk(UINT nFlags, CPoint point);
	afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
	afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
	afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); 
	afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); 
	afx_msg LRESULT OnIdleUpdateCmdUI(WPARAM wParam, LPARAM lParam);
	afx_msg LRESULT OnInitialUpdate(WPARAM wParam, LPARAM lParam);
	afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);

	DECLARE_MESSAGE_MAP()
public:
	virtual void ProcessEvent(Event & e);
	virtual void HandleWindowCreate();
	virtual void HandleWindowDestroy();
	virtual void HandleWindowVisible(bool bVisible);
	virtual void HandleKeyDown(unsigned int key, unsigned int count, unsigned int flags);
	virtual void HandleKeyUp(unsigned int key, unsigned int count, unsigned int flags);
	virtual void HandleLeftDoubleClick(unsigned int flags, const CPoint & p);
	virtual void HandleLeftDown(unsigned int flags, const CPoint & p);
	virtual void HandleLeftUp(unsigned int flags, const CPoint & p);
	virtual void HandleMouseMove(unsigned int flags, const CPoint & p);
	virtual void HandleRightDoubleClick(unsigned int flags, const CPoint & p);
	virtual void HandleRightDown(unsigned int flags, const CPoint & p);
	virtual void HandleRightUp(unsigned int flags, const CPoint & p);
	virtual bool HandleCursor(unsigned int flags, const CPoint & p);
	virtual void HandleFont();
};

#if (_MFC_VER < 0x0800)

BEGIN_MESSAGE_MAP_TEMPLATE1(Window)
	ON_WM_CREATE()
	ON_WM_DESTROY()
	ON_WM_SIZE()
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()
	ON_WM_KEYDOWN()
	ON_WM_KEYUP()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_RBUTTONDBLCLK()
	ON_WM_RBUTTONDOWN()
	ON_WM_RBUTTONUP()
	ON_WM_SETCURSOR()
	ON_WM_HSCROLL()
	ON_WM_VSCROLL()
	ON_MESSAGE(WM_IDLEUPDATECMDUI, OnIdleUpdateCmdUI)
	ON_MESSAGE(WM_INITIALUPDATE, OnInitialUpdate)
	ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
END_MESSAGE_MAP()

#else

BEGIN_TEMPLATE_MESSAGE_MAP(Window, T, T)
	ON_WM_CREATE()
	ON_WM_DESTROY()
	ON_WM_SIZE()
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()
	ON_WM_KEYDOWN()
	ON_WM_KEYUP()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_MOUSEMOVE()
	ON_WM_RBUTTONDBLCLK()
	ON_WM_RBUTTONDOWN()
	ON_WM_RBUTTONUP()
	ON_WM_SETCURSOR()
	ON_WM_HSCROLL()
	ON_WM_VSCROLL()
	ON_MESSAGE(WM_IDLEUPDATECMDUI, OnIdleUpdateCmdUI)
	ON_MESSAGE(WM_INITIALUPDATE, OnInitialUpdate)
	ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
END_MESSAGE_MAP()

#endif

template <class T>
Window<T>::Window()
{ 
	m_ValidBounds = false; 
	m_Inside = false;
	m_pFocusControl = 0;
	m_pCaptureControl = 0;
}

template <class T>
Window<T>::~Window()
{
	
}

template <class T>
CRect Window<T>::Bounds() const
{
	if (m_hWnd == 0)
		return CRect(0, 0, 0, 0);
	CRect r;
	CWnd::GetWindowRect(&r);
	Container * pParent = Control::Parent();
	if (pParent == 0)
		return r;
	CWnd * pParentWnd = pParent->GetWindow();
	assert(pParentWnd != 0);
	pParentWnd->ScreenToClient(r);
	r.OffsetRect(- pParent->GetWindowOffset());
	return r;
}

template <class T>
bool Window<T>::Visible() const
{
	if (m_hWnd == 0)
		return false;
	return T::IsWindowVisible() == TRUE;
}

template <class T>
CPoint Window<T>::GetWindowOffset() const
{
	CRect r(0, 0, 0, 0);
	if (m_hWnd != 0)
		T::GetClientRect(&r);
	return r.TopLeft();
}

template <class T>
CRect Window<T>::GetWindowRect() const
{
	CRect r(0, 0, 0, 0);
	if (m_hWnd != 0)
		T::GetClientRect(&r);
	return r;
}

template <class T>
void Window<T>::CaptureMouse()
{
	SetCaptureControl(this);
}

template <class T>
void Window<T>::ReleaseMouse()
{
	SetCaptureControl(0);
}

template <class T>
bool Window<T>::HasCapture() const
{
	return CWnd::GetCapture() == this && GetCaptureControl() == this;
}

template <class T>
void Window<T>::SetFocus()
{
#if defined(DEBUG_FOCUS)
	TRACE1("%s::SetFocus()\n", typeid(* this).name());
#endif 
	if (CanGetFocus())
		SetFocusControl(this);
	else if (Parent() != 0)
		Parent()->SetFocus();
}

template <class T>
bool Window<T>::HasFocus() const
{
	return CWnd::GetFocus() == this && GetFocusControl() == this;
}

template <class T>
void Window<T>::SetCaptureControl(Control * pControl)
{
	if (m_pCaptureControl != pControl) 
	{
//		TRACE2("%s.SetCaptureControl(%s)\n", typeid(* this).name(), pControl == 0 ? "0" : typeid(* pControl).name());
		m_pCaptureControl = pControl;
		if (m_pCaptureControl != 0)
			T::SetCapture();
		else
			::ReleaseCapture();
	}
}

template <class T>
void Window<T>::SetFocusControl(Control * pControl)
{
#if defined(DEBUG_FOCUS) 
		{
			const char * szOld;
			if (m_pFocusControl != 0)
				szOld = typeid(* m_pFocusControl).name();
			else
				szOld = "0";
			const char * szNew;
			if (pControl != 0)
				szNew = typeid(* pControl).name();
			else
				szNew = "0";
			const char * szThis = typeid(* this).name();
			TRACE("%s::SetFocusControl(%s) // m_pFocusControl == %s\n", 
				szThis, szNew, szOld);
		}
#endif 
	if (m_pFocusControl != pControl) 
	{
		if (m_pFocusControl != 0)
			m_pFocusControl->ProcessEvent(UnfocusEvent());
		m_pFocusControl = pControl;
		if (m_pFocusControl != 0)
			T::SetFocus();
		if (m_pFocusControl != 0)
			m_pFocusControl->ProcessEvent(FocusEvent());
#if defined(DEBUG_FOCUS) 
		{
			HWND hWnd = ::GetFocus();
			CWnd * pWnd = CWnd::FromHandlePermanent(hWnd);
			CString sFocused;
			if (pWnd != 0)
				sFocused = typeid(* pWnd).name();
			else
				sFocused.Format("(hWnd: %p)", hWnd);
			const char * szControl;
			if (m_pFocusControl != 0)
				szControl = typeid(* m_pFocusControl).name();
			else
				szControl = "0";
			TRACE1("\tm_pFocusControl == %s\n", szControl);
			TRACE1("\tFocused Window == %s\n", sFocused);
		}
#endif 
	}
}

template <class T>
void Window<T>::UpdateBounds()
{
	InvalidateWindowBounds();
}

template <class T>
void Window<T>::InvalidateWindowBounds()
{
	m_ValidBounds = false;
	Invalidate();
}

template <class T>
void Window<T>::UpdateWindowBounds()
{
	if (m_ValidBounds)
		return;
	CRect r = Bounds();
	if (m_hWnd != 0) {
		Control * pParent = My::Control::Parent();
		if (pParent != 0)
			r.OffsetRect(pParent->GetWindowOffset());
		CWnd::SetWindowPos(0, r.left, r.top, r.Width(), r.Height(), NULL);
		CWnd::UpdateWindow();
		m_ValidBounds = true;
	}
}

template <class T>
CRect Window<T>::GetClientRect() const
{
	CRect r(0, 0, 0, 0);
	if (m_hWnd != 0)
		T::GetClientRect(&r);
	return r;
}

template <class T>
BOOL Window<T>::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{
/*	if (nCode == CN_COMMAND) {
		CommandEvent event(nID);
		ProcessEvent(event);
		if (event.m_Handled)
			return TRUE;
	}
	else if (nCode == CN_UPDATE_COMMAND_UI) {
		UpdateCommandUIEvent event(nID, reinterpret_cast<CCmdUI *>(pExtra));
		ProcessEvent(event);
		if (event.m_Handled)
			return TRUE;
	}
*/	return T::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}

template <class T>
int Window<T>::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if (T::OnCreate(lpCreateStruct) == -1)
		return -1;
	m_HintWindow.Create(this, TTS_ALWAYSTIP|TTS_NOPREFIX);
	DispatchEvent(WindowCreateEvent());
	return 0;
}

template <class T>
void Window<T>::OnDestroy()
{
	DispatchEvent(WindowDestroyEvent());
	T::OnDestroy();
}

template <class T>
void Window<T>::OnSize(UINT nType, int cx, int cy)
{
	T::OnSize(nType, cx, cy);
	Container::UpdateBounds();
}

template <class T>
void Window<T>::OnSetFocus(CWnd * pOldWnd)
{
#if defined(DEBUG_FOCUS)
	TRACE1("%s::OnSetFocus()\n", typeid(* this).name());

	CWnd * pWnd = pOldWnd;
	CString sFocused;
	if (pWnd == 0)
		sFocused = "0";
	else
		sFocused = typeid(* pWnd).name();
	const char * szControl;
	if (m_pFocusControl != 0)
		szControl = typeid(* m_pFocusControl).name();
	else
		szControl = "0";
	TRACE1("\tm_pFocusControl == %s\n", szControl);
	TRACE1("\tOld Focused Window == %s\n", sFocused);
#endif
	if (m_pFocusControl == 0)
		SetFocusControl(this);
	T::OnSetFocus(pOldWnd);
}

template <class T>
void Window<T>::OnKillFocus(CWnd * pNewWnd)
{
#if defined(DEBUG_FOCUS)
	TRACE1("%s::OnKillFocus()\n", typeid(* this).name());

	CWnd * pWnd = pNewWnd;
	CString sFocused;
	if (pWnd == 0)
		sFocused = "0";
	else
		sFocused = typeid(* pWnd).name();		
	const char * szControl;
	if (m_pFocusControl != 0)
		szControl = typeid(* m_pFocusControl).name();
	else
		szControl = "0";
	TRACE1("\tm_pFocusControl == %s\n", szControl);
	TRACE1("\tNew Focused Window == %s\n", sFocused);
#endif 
	T::OnKillFocus(pNewWnd);
	SetFocusControl(0);
}

template <class T>
void Window<T>::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	ProcessEvent(KeyDownEvent(nChar, nRepCnt, nFlags));
}

template <class T>
void Window<T>::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	ProcessEvent(KeyUpEvent(nChar, nRepCnt, nFlags));
}

template <class T>
void Window<T>::OnLButtonDblClk(UINT nFlags, CPoint point) 
{
	ProcessEvent(LeftDoubleClickEvent(nFlags, point));
}

template <class T>
void Window<T>::OnLButtonDown(UINT nFlags, CPoint point) 
{
	ProcessEvent(LeftDownEvent(nFlags, point));
}

template <class T>
void Window<T>::OnLButtonUp(UINT nFlags, CPoint point) 
{
	ProcessEvent(LeftUpEvent(nFlags, point));
}

template <class T>
void Window<T>::OnMouseMove(UINT nFlags, CPoint point) 
{
	if (!m_Inside) 
	{
		m_Inside = true;

		ProcessEvent(MouseEnterEvent(nFlags, point));
		
		TRACKMOUSEEVENT tme;
		tme.cbSize = sizeof(tme);
		tme.dwFlags = TME_LEAVE;
		tme.hwndTrack = m_hWnd;
		tme.dwHoverTime = 0;		
		VERIFY(_TrackMouseEvent(&tme));
	}

	ProcessEvent(MouseMoveEvent(nFlags, point));
}

template <class T>
void Window<T>::OnRButtonDblClk(UINT nFlags, CPoint point) 
{
	ProcessEvent(RightDoubleClickEvent(nFlags, point));
}

template <class T>
void Window<T>::OnRButtonDown(UINT nFlags, CPoint point) 
{
	ProcessEvent(RightDownEvent(nFlags, point));
}

template <class T>
void Window<T>::OnRButtonUp(UINT nFlags, CPoint point) 
{
	ProcessEvent(RightUpEvent(nFlags, point));
}

template <class T>
BOOL Window<T>::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	UINT nFlags = 0;
	CPoint point;
	GetCursorPos(&point);
	T::ScreenToClient(&point);

	CursorEvent event(nFlags, point);
	ProcessEvent(event);
	if (event.m_Handled)
		return TRUE;
	else
		return T::OnSetCursor(pWnd, nHitTest, message);
}

template <class T>
void Window<T>::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if (pScrollBar != 0)
		pScrollBar->SendMessage(WM_HSCROLL, MAKELONG(nSBCode, nPos), 0);
	else
		T::OnHScroll(nSBCode, nPos, pScrollBar);
}

template <class T>
void Window<T>::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	if (pScrollBar != 0)
		pScrollBar->SendMessage(WM_VSCROLL, MAKELONG(nSBCode, nPos), 0);
	else
		T::OnVScroll(nSBCode, nPos, pScrollBar);
}

template <class T>
LRESULT Window<T>::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM lParam)
{
	ProcessEvent(UpdateUIEvent());
	return LRESULT(FALSE);
}

template <class T>
LRESULT Window<T>::OnInitialUpdate(WPARAM wParam, LPARAM lParam)
{
	ProcessEvent(UpdateUIEvent());
	return LRESULT(FALSE);
}

template <class T>
LRESULT Window<T>::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
	ProcessEvent(MouseLeaveEvent());
	m_Inside = false;
	return TRUE;
}

template <class T>
void Window<T>::ProcessEvent(Event & event)
{
	if (event.Is<WindowCreateEvent>())
	{
		HandleWindowCreate();
		return;
	}
	if (event.Is<WindowDestroyEvent>())
	{
		HandleWindowDestroy();
		return;
	}
	if (event.Is<WindowBoundsEvent>())
	{
		WindowBoundsEvent & e = event.As<WindowBoundsEvent>();
		if (e.m_Update)
			UpdateWindowBounds();
		else
			InvalidateWindowBounds();
		return;
	}
	if (event.Is<WindowVisibleEvent>())
	{
		WindowVisibleEvent & e = event.As<WindowVisibleEvent>();
		HandleWindowVisible(e.m_Visible);
		return;
	}
	if (event.Is<CursorEvent>())
	{
		CursorEvent & e = event.As<CursorEvent>();
		if (m_pCaptureControl != 0 && m_pCaptureControl != this) 
		{
			e.m_Point -= m_pCaptureControl->GetWindowOffset();
			m_pCaptureControl->ProcessEvent(event);
			if (e.m_Handled)
				return;
		}
	}
	if (event.Is<MouseEvent>()) 
	{
		MouseEvent & e = event.As<MouseEvent>();
		if (m_pCaptureControl != 0 && m_pCaptureControl != this)
		{
			e.m_Point -= m_pCaptureControl->GetWindowOffset();
			m_pCaptureControl->ProcessEvent(event);
			return;
		}
	}
	if (event.Is<KeyEvent>()) 
	{
		if (m_pFocusControl != 0 && m_pFocusControl != this) 
		{
			m_pFocusControl->ProcessEvent(event);
			return;
		}
	}
	Container::ProcessEvent(event);
}

template <class T>
void Window<T>::HandleKeyDown(unsigned int key, unsigned int count, unsigned int flags)
{
	T::Default();
}

template <class T>
void Window<T>::HandleKeyUp(unsigned int key, unsigned int count, unsigned int flags)
{
	T::Default();
}

template <class T>
void Window<T>::HandleLeftDoubleClick(unsigned int flags, const CPoint & p)
{
	T::Default();
}

template <class T>
void Window<T>::HandleLeftDown(unsigned int flags, const CPoint & p)
{
	Control::HandleLeftDown(flags, p);
	T::Default();
}

template <class T>
void Window<T>::HandleLeftUp(unsigned int flags, const CPoint & p)
{
	Control::HandleLeftUp(flags, p);
	T::Default();
}

template <class T>
void Window<T>::HandleMouseMove(unsigned int flags, const CPoint & p)
{
	Control::HandleMouseMove(flags, p);
	T::Default();
}

template <class T>
void Window<T>::HandleRightDoubleClick(unsigned int flags, const CPoint & p)
{
	T::Default();
}

template <class T>
void Window<T>::HandleRightDown(unsigned int flags, const CPoint & p)
{
	T::Default();
}

template <class T>
void Window<T>::HandleRightUp(unsigned int flags, const CPoint & p)
{
	T::Default();
}

template <class T>
bool Window<T>::HandleCursor(unsigned int flags, const CPoint & p)
{
	return T::Default() != 0;//false;
}

template <class T>
void Window<T>::HandleWindowCreate()
{
	assert(m_hWnd == NULL);
	DoCreateWindow();
	CFont * pFont = const_cast<CFont *>(Font());
	if (pFont != 0)
		T::SetFont(pFont);
	UpdateWindowBounds();
}

template <class T>
void Window<T>::HandleWindowDestroy()
{
	assert(m_hWnd != 0);
	T::DestroyWindow();
}

template <class T>
void Window<T>::HandleWindowVisible(bool bVisible)
{
	if (m_hWnd != 0)
		T::ShowWindow(bVisible ? SW_SHOW : SW_HIDE);
	else {
		if (bVisible)
			m_Style |= WS_VISIBLE;
		else
			m_Style &= ~WS_VISIBLE;
	}
}

template <class T>
void Window<T>::HandleFont()
{
	if (m_hWnd != 0) {
		CFont * pFont = const_cast<CFont *>(Font());
		if (pFont != 0)
			T::SetFont(pFont);
	}
}

template <class T>
void Window<T>::DoCreateWindow()
{
	VERIFY(CWnd::Create(m_Class, NULL, m_Style, Bounds(), Control::GetWindow(), 0));
}


} // My
